/**
 * react-native-swiper
 * @author leecade<leecade@163.com>
 */
 import React, { Component } from 'react'
 import PropTypes from 'prop-types'
 import {
   Text,
   View,
   ViewPropTypes,
   ScrollView,
   Dimensions,
   TouchableOpacity,
   Platform,
   ActivityIndicator
 } from 'react-native'
 
 /**
  * Default styles
  * @type {StyleSheetPropType}
  */
 const styles = {
   container: {
     backgroundColor: 'transparent',
     position: 'relative',
     flex: 1
   },
 
   wrapperIOS: {
     backgroundColor: 'transparent'
   },
 
   wrapperAndroid: {
     backgroundColor: 'transparent',
     flex: 1
   },
 
   slide: {
     backgroundColor: 'transparent'
   },
 
   pagination_x: {
     position: 'absolute',
     bottom: 25,
     left: 0,
     right: 0,
     flexDirection: 'row',
     flex: 1,
     justifyContent: 'center',
     alignItems: 'center',
     backgroundColor: 'transparent'
   },
 
   pagination_y: {
     position: 'absolute',
     right: 15,
     top: 0,
     bottom: 0,
     flexDirection: 'column',
     flex: 1,
     justifyContent: 'center',
     alignItems: 'center',
     backgroundColor: 'transparent'
   },
 
   title: {
     height: 30,
     justifyContent: 'center',
     position: 'absolute',
     paddingLeft: 10,
     bottom: -30,
     left: 0,
     flexWrap: 'nowrap',
     width: 250,
     backgroundColor: 'transparent'
   },
 
   buttonWrapper: {
     backgroundColor: 'transparent',
     flexDirection: 'row',
     position: 'absolute',
     top: 0,
     left: 0,
     flex: 1,
     paddingHorizontal: 10,
     paddingVertical: 10,
     justifyContent: 'space-between',
     alignItems: 'center'
   },
 
   buttonText: {
     fontSize: 50,
     color: '#007aff'
   }
 }
 
 // missing `module.exports = exports['default'];` with babel6
 // export default React.createClass({
 export default class extends Component {
   /**
    * Props Validation
    * @type {Object}
    */
   static propTypes = {
     horizontal: PropTypes.bool,
     children: PropTypes.node.isRequired,
     containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
     style: PropTypes.oneOfType([
       PropTypes.object,
       PropTypes.number,
       PropTypes.array
     ]),
     scrollViewStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
     pagingEnabled: PropTypes.bool,
     showsHorizontalScrollIndicator: PropTypes.bool,
     showsVerticalScrollIndicator: PropTypes.bool,
     bounces: PropTypes.bool,
     scrollsToTop: PropTypes.bool,
     removeClippedSubviews: PropTypes.bool,
     automaticallyAdjustContentInsets: PropTypes.bool,
     showsPagination: PropTypes.bool,
     showsButtons: PropTypes.bool,
     disableNextButton: PropTypes.bool,
     disablePrevButton: PropTypes.bool,
     loadMinimal: PropTypes.bool,
     loadMinimalSize: PropTypes.number,
     loadMinimalLoader: PropTypes.element,
     loop: PropTypes.bool,
     autoplay: PropTypes.bool,
     autoplayTimeout: PropTypes.number,
     autoplayDirection: PropTypes.bool,
     index: PropTypes.number,
     renderPagination: PropTypes.func,
     dotStyle: PropTypes.oneOfType([
       PropTypes.object,
       PropTypes.number,
       PropTypes.array
     ]),
     activeDotStyle: PropTypes.oneOfType([
       PropTypes.object,
       PropTypes.number,
       PropTypes.array
     ]),
     dotColor: PropTypes.string,
     activeDotColor: PropTypes.string,
     /**
      * Called when the index has changed because the user swiped.
      */
     onIndexChanged: PropTypes.func
   }
 
   /**
    * Default props
    * @return {object} props
    * @see http://facebook.github.io/react-native/docs/scrollview.html
    */
   static defaultProps = {
     horizontal: true,
     pagingEnabled: true,
     showsHorizontalScrollIndicator: false,
     showsVerticalScrollIndicator: false,
     bounces: false,
     scrollsToTop: false,
     removeClippedSubviews: true,
     automaticallyAdjustContentInsets: false,
     showsPagination: true,
     showsButtons: false,
     disableNextButton: false,
     disablePrevButton: false,
     loop: true,
     loadMinimal: false,
     loadMinimalSize: 1,
     autoplay: false,
     autoplayTimeout: 2.5,
     autoplayDirection: true,
     index: 0,
     onIndexChanged: () => null
   }
 
   /**
    * Init states
    * @return {object} states
    */
   state = this.initState(this.props)
 
   /**
    * Initial render flag
    * @type {bool}
    */
   initialRender = true
 
   /**
    * autoplay timer
    * @type {null}
    */
   autoplayTimer = null
   loopJumpTimer = null
 
   UNSAFE_componentWillReceiveProps(nextProps) {
     if (!nextProps.autoplay && this.autoplayTimer)
       clearTimeout(this.autoplayTimer)
     if (nextProps.index === this.props.index) return
     this.setState(
       this.initState(nextProps, this.props.index !== nextProps.index)
     )
   }
 
   componentDidMount() {
     this.autoplay()
   }
 
   componentWillUnmount() {
     this.autoplayTimer && clearTimeout(this.autoplayTimer)
     this.loopJumpTimer && clearTimeout(this.loopJumpTimer)
   }
 
   UNSAFE_componentWillUpdate(nextProps, nextState) {
     // If the index has changed, we notify the parent via the onIndexChanged callback
     if (this.state.index !== nextState.index)
       this.props.onIndexChanged(nextState.index)
   }
 
   componentDidUpdate(prevProps) {
     // If autoplay props updated to true, autoplay immediately
     if (this.props.autoplay && !prevProps.autoplay) {
       this.autoplay()
     }
     if (this.props.children !== prevProps.children) {
       if (this.props.loadMinimal && Platform.OS === 'ios') {
         this.setState({ ...this.props, index: this.state.index })
       } else {
         this.setState(
           this.initState({ ...this.props, index: this.state.index }, true)
         )
       }
     }
   }
 
   initState(props, updateIndex = false) {
     // set the current state
     const state = this.state || { width: 0, height: 0, offset: { x: 0, y: 0 } }
 
     const initState = {
       autoplayEnd: false,
       children: null,
       loopJump: false,
       offset: {}
     }
 
     // Support Optional render page
     initState.children = Array.isArray(props.children)
       ? props.children.filter(child => child)
       : props.children
 
     initState.total = initState.children ? initState.children.length || 1 : 0
 
     if (state.total === initState.total && !updateIndex) {
       // retain the index
       initState.index = state.index
     } else {
       initState.index =
         initState.total > 1 ? Math.min(props.index, initState.total - 1) : 0
     }
 
     // Default: horizontal
     const { width, height } = Dimensions.get('window')
 
     initState.dir = props.horizontal === false ? 'y' : 'x'
 
     if (props.width) {
       initState.width = props.width
     } else if (this.state && this.state.width) {
       initState.width = this.state.width
     } else {
       initState.width = width
     }
 
     if (props.height) {
       initState.height = props.height
     } else if (this.state && this.state.height) {
       initState.height = this.state.height
     } else {
       initState.height = height
     }
 
     initState.offset[initState.dir] =
       initState.dir === 'y' ? initState.height * props.index : initState.width * props.index
 
     this.internals = {
       ...this.internals,
       isScrolling: false
     }
     return initState
   }
 
   // include internals with state
   fullState() {
     return Object.assign({}, this.state, this.internals)
   }
 
   onLayout = event => {
     const { width, height } = event.nativeEvent.layout
     const offset = (this.internals.offset = {})
     const state = { width, height }
 
     if (this.state.total > 1) {
       let setup = this.state.index
       if (this.props.loop) {
         setup++
       }
       offset[this.state.dir] =
         this.state.dir === 'y' ? height * setup : width * setup
     }
 
     // only update the offset in state if needed, updating offset while swiping
     // causes some bad jumping / stuttering
     if (!this.state.offset) {
       state.offset = offset
     }
 
     // related to https://github.com/leecade/react-native-swiper/issues/570
     // contentOffset is not working in react 0.48.x so we need to use scrollTo
     // to emulate offset.
     if(this.state.total > 1) {
       this.scrollView.scrollTo({ ...offset, animated: false })
     }
   
     if (this.initialRender) {
       this.initialRender = false
     }
 
     this.setState(state)
   }
 
   loopJump = () => {
     if (!this.state.loopJump) return
     const i = this.state.index + (this.props.loop ? 1 : 0)
     const scrollView = this.scrollView
     this.loopJumpTimer = setTimeout(
       () => {
         if (scrollView.setPageWithoutAnimation) {
           scrollView.setPageWithoutAnimation(i)
         } else {
           if (this.state.index === 0) {
             scrollView.scrollTo(
               this.props.horizontal === false
                 ? { x: 0, y: this.state.height, animated: false }
                 : { x: this.state.width, y: 0, animated: false }
             )
           } else if (this.state.index === this.state.total - 1) {
             this.props.horizontal === false
               ? this.scrollView.scrollTo({
                   x: 0,
                   y: this.state.height * this.state.total,
                   animated: false
                 })
               : this.scrollView.scrollTo({
                   x: this.state.width * this.state.total,
                   y: 0,
                   animated: false
                 })
           }
         }
       },
       // Important Parameter
       // ViewPager 50ms, ScrollView 300ms
       scrollView.setPageWithoutAnimation ? 50 : 300
     )
   }
 
   /**
    * Automatic rolling
    */
   autoplay = () => {
     if (
       !Array.isArray(this.state.children) ||
       !this.props.autoplay ||
       this.internals.isScrolling ||
       this.state.autoplayEnd
     )
       return
 
     this.autoplayTimer && clearTimeout(this.autoplayTimer)
     this.autoplayTimer = setTimeout(() => {
       if (
         !this.props.loop &&
         (this.props.autoplayDirection
           ? this.state.index === this.state.total - 1
           : this.state.index === 0)
       )
         return this.setState({ autoplayEnd: true })
 
       this.scrollBy(this.props.autoplayDirection ? 1 : -1)
     }, this.props.autoplayTimeout * 1000)
   }
 
   /**
    * Scroll begin handle
    * @param  {object} e native event
    */
   onScrollBegin = e => {
     // update scroll state
     this.internals.isScrolling = true
     this.props.onScrollBeginDrag &&
       this.props.onScrollBeginDrag(e, this.fullState(), this)
   }
 
   /**
    * Scroll end handle
    * @param  {object} e native event
    */
   onScrollEnd = e => {
     // update scroll state
     this.internals.isScrolling = false
 
     // making our events coming from android compatible to updateIndex logic
     if (!e.nativeEvent.contentOffset) {
       if (this.state.dir === 'x') {
         e.nativeEvent.contentOffset = {
           x: e.nativeEvent.position * this.state.width
         }
       } else {
         e.nativeEvent.contentOffset = {
           y: e.nativeEvent.position * this.state.height
         }
       }
     }
 
     this.updateIndex(e.nativeEvent.contentOffset, this.state.dir, () => {
       this.autoplay()
       this.loopJump()
     })
     // if `onMomentumScrollEnd` registered will be called here
     this.props.onMomentumScrollEnd &&
       this.props.onMomentumScrollEnd(e, this.fullState(), this)
   }
 
   /*
    * Drag end handle
    * @param {object} e native event
    */
   onScrollEndDrag = e => {
     const { contentOffset } = e.nativeEvent
     const { horizontal } = this.props
     const { children, index } = this.state
     const { offset } = this.internals
     const previousOffset = horizontal ? offset.x : offset.y
     const newOffset = horizontal ? contentOffset.x : contentOffset.y
 
     if (
       previousOffset === newOffset &&
       (index === 0 || index === children.length - 1)
     ) {
       this.internals.isScrolling = false
     }
   }
 
   /**
    * Update index after scroll
    * @param  {object} offset content offset
    * @param  {string} dir    'x' || 'y'
    */
   updateIndex = (offset, dir, cb) => {
     const state = this.state
     // Android ScrollView will not scrollTo certain offset when props change
     let index = state.index
     if (!this.internals.offset)
       // Android not setting this onLayout first? https://github.com/leecade/react-native-swiper/issues/582
       this.internals.offset = {}
     const diff = offset[dir] - (this.internals.offset[dir] || 0)
     const step = dir === 'x' ? state.width : state.height
     let loopJump = false
 
     // Do nothing if offset no change.
     if (!diff) return
 
     // Note: if touch very very quickly and continuous,
     // the variation of `index` more than 1.
     // parseInt() ensures it's always an integer
     index = parseInt(index + Math.round(diff / step))
 
     if (this.props.loop) {
       if (index <= -1) {
         index = state.total - 1
         offset[dir] = step * state.total
         loopJump = true
       } else if (index >= state.total) {
         index = 0
         offset[dir] = step
         loopJump = true
       }
     }
 
     const newState = {}
     newState.index = index
     newState.loopJump = loopJump
 
     this.internals.offset = offset
 
     // only update offset in state if loopJump is true
     if (loopJump) {
       // when swiping to the beginning of a looping set for the third time,
       // the new offset will be the same as the last one set in state.
       // Setting the offset to the same thing will not do anything,
       // so we increment it by 1 then immediately set it to what it should be,
       // after render.
       if (offset[dir] === this.internals.offset[dir]) {
         newState.offset = { x: 0, y: 0 }
         newState.offset[dir] = offset[dir] + 1
         this.setState(newState, () => {
           this.setState({ offset: offset }, cb)
         })
       } else {
         newState.offset = offset
         this.setState(newState, cb)
       }
     } else {
       this.setState(newState, cb)
     }
   }
 
   /**
    * Scroll by index
    * @param  {number} index offset index
    * @param  {bool} animated
    */
 
   scrollBy = (index, animated = true) => {
     if (this.internals.isScrolling || this.state.total < 2) return
     const state = this.state
     const diff = (this.props.loop ? 1 : 0) + index + this.state.index
     let x = 0
     let y = 0
     if (state.dir === 'x') x = diff * state.width
     if (state.dir === 'y') y = diff * state.height
 
     this.scrollView && this.scrollView.scrollTo({ x, y, animated })
 
     // update scroll state
     this.internals.isScrolling = true
     this.setState({
       autoplayEnd: false
     })
 
     // trigger onScrollEnd manually in android
     if (!animated || Platform.OS !== 'ios') {
       setImmediate(() => {
         this.onScrollEnd({
           nativeEvent: {
             position: diff
           }
         })
       })
     }
   }
 
   /**
    * Scroll to index
    * @param  {number} index page
    * @param  {bool} animated
    */
 
   scrollTo = (index, animated = true) => {
     if (
       this.internals.isScrolling ||
       this.state.total < 2 ||
       index == this.state.index
     )
       return
 
     const state = this.state
     const diff = this.state.index + (index - this.state.index)
 
     let x = 0
     let y = 0
     if (state.dir === 'x') x = diff * state.width
     if (state.dir === 'y') y = diff * state.height
 
     this.scrollView && this.scrollView.scrollTo({ x, y, animated })
 
     // update scroll state
     this.internals.isScrolling = true
     this.setState({
       autoplayEnd: false
     })
 
     // trigger onScrollEnd manually in android
     if (!animated || Platform.OS !== 'ios') {
       setImmediate(() => {
         this.onScrollEnd({
           nativeEvent: {
             position: diff
           }
         })
       })
     }
   }
 
   scrollViewPropOverrides = () => {
     const props = this.props
     let overrides = {}
 
     /*
     const scrollResponders = [
       'onMomentumScrollBegin',
       'onTouchStartCapture',
       'onTouchStart',
       'onTouchEnd',
       'onResponderRelease',
     ]
     */
 
     for (let prop in props) {
       // if(~scrollResponders.indexOf(prop)
       if (
         typeof props[prop] === 'function' &&
         prop !== 'onMomentumScrollEnd' &&
         prop !== 'renderPagination' &&
         prop !== 'onScrollBeginDrag'
       ) {
         let originResponder = props[prop]
         overrides[prop] = e => originResponder(e, this.fullState(), this)
       }
     }
 
     return overrides
   }
 
   /**
    * Render pagination
    * @return {object} react-dom
    */
   renderPagination = () => {
     // By default, dots only show when `total` >= 2
     if (this.state.total <= 1) return null
 
     let dots = []
     const ActiveDot = this.props.activeDot || (
       <View
         style={[
           {
             backgroundColor: this.props.activeDotColor || '#007aff',
             width: 8,
             height: 8,
             borderRadius: 4,
             marginLeft: 3,
             marginRight: 3,
             marginTop: 3,
             marginBottom: 3
           },
           this.props.activeDotStyle
         ]}
       />
     )
     const Dot = this.props.dot || (
       <View
         style={[
           {
             backgroundColor: this.props.dotColor || 'rgba(0,0,0,.2)',
             width: 8,
             height: 8,
             borderRadius: 4,
             marginLeft: 3,
             marginRight: 3,
             marginTop: 3,
             marginBottom: 3
           },
           this.props.dotStyle
         ]}
       />
     )
     for (let i = 0; i < this.state.total; i++) {
       dots.push(
         i === this.state.index
           ? React.cloneElement(ActiveDot, { key: i })
           : React.cloneElement(Dot, { key: i })
       )
     }
 
     return (
       <View
         pointerEvents="none"
         style={[
           styles['pagination_' + this.state.dir],
           this.props.paginationStyle
         ]}
       >
         {dots}
       </View>
     )
   }
 
   renderTitle = () => {
     const child = this.state.children[this.state.index]
     const title = child && child.props && child.props.title
     return title ? (
       <View style={styles.title}>
         {this.state.children[this.state.index].props.title}
       </View>
     ) : null
   }
 
   renderNextButton = () => {
     let button = null
 
     if (this.props.loop || this.state.index !== this.state.total - 1) {
       button = this.props.nextButton || <Text style={styles.buttonText}>›</Text>
     }
 
     return (
       <TouchableOpacity
         onPress={() => button !== null && this.scrollBy(1)}
         disabled={this.props.disableNextButton}
       >
         <View>{button}</View>
       </TouchableOpacity>
     )
   }
 
   renderPrevButton = () => {
     let button = null
 
     if (this.props.loop || this.state.index !== 0) {
       button = this.props.prevButton || <Text style={styles.buttonText}>‹</Text>
     }
 
     return (
       <TouchableOpacity
         onPress={() => button !== null && this.scrollBy(-1)}
         disabled={this.props.disablePrevButton}
       >
         <View>{button}</View>
       </TouchableOpacity>
     )
   }
 
   renderButtons = () => {
     return (
       <View
         pointerEvents="box-none"
         style={[
           styles.buttonWrapper,
           {
             width: this.state.width,
             height: this.state.height
           },
           this.props.buttonWrapperStyle
         ]}
       >
         {this.renderPrevButton()}
         {this.renderNextButton()}
       </View>
     )
   }
 
   refScrollView = view => {
     this.scrollView = view
   }
 
   onPageScrollStateChanged = state => {
     switch (state) {
       case 'dragging':
         return this.onScrollBegin()
 
       case 'idle':
       case 'settling':
         if (this.props.onTouchEnd) this.props.onTouchEnd()
     }
   }
 
   renderScrollView = pages => {
     return (
       <ScrollView
         ref={this.refScrollView}
         {...this.props}
         {...this.scrollViewPropOverrides()}
         contentContainerStyle={[styles.wrapperIOS, this.props.style]}
         contentOffset={this.state.offset}
         onScrollBeginDrag={this.onScrollBegin}
         onMomentumScrollEnd={this.onScrollEnd}
         onScrollEndDrag={this.onScrollEndDrag}
         style={this.props.scrollViewStyle}
       >
         {pages}
       </ScrollView>
     )
   }
 
   /**
    * Default render
    * @return {object} react-dom
    */
   render() {
     const { index, total, width, height, children } = this.state
     const {
       containerStyle,
       loop,
       loadMinimal,
       loadMinimalSize,
       loadMinimalLoader,
       renderPagination,
       showsButtons,
       showsPagination
     } = this.props
     // let dir = state.dir
     // let key = 0
     const loopVal = loop ? 1 : 0
     let pages = []
 
     const pageStyle = [{ width: width, height: height }, styles.slide]
     const pageStyleLoading = {
       width,
       height,
       flex: 1,
       justifyContent: 'center',
       alignItems: 'center'
     }
 
     // For make infinite at least total > 1
     if (total > 1) {
       // Re-design a loop model for avoid img flickering
       pages = Object.keys(children)
       if (loop) {
         pages.unshift(total - 1 + '')
         pages.push('0')
       }
 
       pages = pages.map((page, i) => {
         if (loadMinimal) {
           if (
             (i >= index + loopVal - loadMinimalSize &&
               i <= index + loopVal + loadMinimalSize) ||
             // The real first swiper should be keep
             (loop && i === 1) ||
             // The real last swiper should be keep
             (loop && i === total - 1)
           ) {
             return (
               <View style={pageStyle} key={i}>
                 {children[page]}
               </View>
             )
           } else {
             return (
               <View style={pageStyleLoading} key={i}>
                 {loadMinimalLoader ? loadMinimalLoader : <ActivityIndicator />}
               </View>
             )
           }
         } else {
           return (
             <View style={pageStyle} key={i}>
               {children[page]}
             </View>
           )
         }
       })
     } else {
       pages = (
         <View style={pageStyle} key={0}>
           {children}
         </View>
       )
     }
 
     return (
       <View style={[styles.container, containerStyle]} onLayout={this.onLayout}>
         {this.renderScrollView(pages)}
         {showsPagination &&
           (renderPagination
             ? renderPagination(index, total, this)
             : this.renderPagination())}
         {this.renderTitle()}
         {showsButtons && this.renderButtons()}
       </View>
     )
   }
 }
 